// Copyright (c) [2019] [huazhonghua]
// [理解] is licensed under the Mulan PSL v1.
// You can use this software according to the terms and conditions of the Mulan PSL v1.
// You may obtain a copy of Mulan PSL v1 at:
//     http://license.coscl.org.cn/MulanPSL
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v1 for more details.

#ifndef EXECUTION_H
#define EXECUTION_H

#include <pthread.h>
#include <stdio.h>

#include "structure.h"
#include "basic.h"

// run status
#define OK 1
#define REQUEUE 2
#define WAITING 3

// request status
#define REQUEST_IN_PROGRESS 1
#define REQUEST_DONE 2

typedef struct ExecutionData ExecutionData;

struct ExecutionData {
	Node* all;
	Node* request;
	Node* context_stack;
	Node* context;
	Node* run_stack;
	Node* run_data;
	Node* steps;
	Node* step_index;
	Node* (*get_step)(ExecutionData*);

	FILE* log;
};

void make_runnable(Node* all, Node* node, int use_create) {
	if(node == NULL) return;

	Node* left = get_linked(node, LEFT);
	Node* operation = get_linked(node, OPERATION);
	Node* runner;
	if(operation != NULL) {
		if(is_tier_1(operation, ASSIGN)) {
			if(is_instance_of(left, VARIABLE)) {
				link_node(node, STEP, get_linked(all, ASSIGN_VARIABLE_STEP));
				runner = get_linked(all, STEP_RUNNER);
			}
			else if(is_operation_type(left, LINK_ACCESS)) {
				link_node(node, STEP, get_linked(all, ASSIGN_LINK_STEP));
				runner = get_linked(all, STEP_RUNNER);
			}
			else if(is_operation_type(left, ARRAY_ACCESS)) {
				link_node(node, STEP, get_linked(all, ASSIGN_ARRAY_ITEM_STEP));
				runner = get_linked(all, STEP_RUNNER);
			}
			else {
				printf("error\n");
			}
			use_create = 1;
		}
		else if(is_tier_1(operation, LINK_ACCESS)) {
			int part = use_create ? GET_CREATE_LINKED_STEP : GET_LINKED_STEP;
			link_node(node, STEP, get_linked(all, part));
			runner = get_linked(all, STEP_RUNNER);
		}
		else if(is_tier_1(operation, ARRAY_ACCESS)) {
			int part = use_create ? GET_CREATE_ARRAY_ITEM_STEP : GET_ARRAY_ITEM_STEP;
			link_node(node, STEP, get_linked(all, part));
			runner = get_linked(all, STEP_RUNNER);
		}
		else if(is_tier_1(operation, UNORDERED)) {
			runner = get_linked(all, UNORDERED_RUNNER);
		}
		else {
			runner = get_linked(all, OPERATION_RUNNER);
		}

		link_node(node, RUNNER, runner);
	}
	else if(is_instance_of(node, VARIABLE)) {
		int part = use_create ? GET_CREATE_VARIABLE : GET_VARIABLE;
		runner = get_linked(all, part);
		link_node(node, RUNNER, runner);
	}
	else if(is_instance_of(node, LIST)) {
		List* list = node->other.pointer;
		for(int i = 0; i < list->length; ++i) {
			Node* arg = list->nodes[i];
			make_runnable(all, arg, use_create);
		}
	}

	make_runnable(all, left, use_create);

	Node* right = get_linked(node, RIGHT);
	make_runnable(all, right, 0);

	Node* arguments = get_linked(node, ARGUMENT);
	make_runnable(all, arguments, 0);
}

Node* get_list_step(ExecutionData* execution) {
	return get_list_node_item(execution->steps, execution->step_index->part);
}

Node* get_step(ExecutionData* execution) {
	return execution->step_index->part == 0 ? execution->steps : NULL;
}

Node* create_run_data(Node* all, Node* steps, Node* run_stack, Node* step_index) {
	Node* run_data = create_node(all);
	link_node(run_data, STEP, steps);
	link_node(run_data, STEP_INDEX, step_index);
	add_to_list_node(run_stack, run_data);

	Node* dispose_list = create_list_node(all, 4);
	link_node(run_data, DISPOSE_LIST, dispose_list);

	return run_data;
}

Node* create_context(Node* all, Node* context_stack) {
	Node* context = create_node(all);

	link_node(context, ALL, all);
	link_node(context, DISPOSE_LIST, create_list_node(all, 4));
	add_to_list_node(context_stack, context);
	return context;
}

Node* create_request(Node* all, Node* context_stack, Node* steps) {
	Node* request = create_node(all);

	Node* run_stack = create_list_node(all, 1);
	Node* step_index = create_node(all);
	step_index->part = 0;
	create_run_data(all, steps, run_stack, step_index);

	link_node(request, CONTEXT_STACK, context_stack);
	link_node(request, RUN_STACK, run_stack);
	link_node(request, ALL, all);

	return request;
}

void free_request(Node* request) {
	Node* context_stack = get_linked(request, CONTEXT_STACK);
	free_list_node(context_stack);

	Node* run_stack = get_linked(request, RUN_STACK);
	free_list_node(run_stack);

	free_node(request);
}

void make_disposable(Node* all, Node* dispose_list, Node* node) {
	link_node(node, DISPOSABLE, get_create_int(all, 1));
	add_to_list_node(dispose_list, node);
}

void dispose(Node* dispose_list) {
	int length = get_list_node_length(dispose_list);
	for(int i = 0; i < length; ++i) {
		Node* node = get_list_node_item(dispose_list, i);
		Node* disposable = get_linked(node, DISPOSABLE);
		if(disposable != NULL && disposable->part == 1) {
			if(is_instance_of(node, LIST)) {
				free_list_node(node);
			}
			else {
				free_node(node);
			}
		}
	}
	free_list_node(dispose_list);
}

Node* create_context_stack(Node* all) {
	Node* context_stack = create_list_node(all, 1);
	create_context(all, context_stack);
	return context_stack;
}

void push_new_run_data(ExecutionData* execution, Node* steps) {
	Node* step_index = create_node(execution->all);
	step_index->part = -1;
	execution->run_data = create_run_data(execution->all, steps, execution->run_stack, step_index);
	execution->steps = steps;
	execution->step_index = step_index;

	execution->get_step = is_instance_of(execution->steps, LIST) ? get_list_step : get_step;
}

int pop_run_data(ExecutionData* execution) {
	List* run_stack_list = execution->run_stack->other.pointer;
	if(run_stack_list->length <= 0) return 0;

	Node* dispose_list = get_linked(execution->run_data, DISPOSE_LIST);
	dispose(dispose_list);

	--(run_stack_list->length);

	Node* step_index = get_linked(execution->run_data, STEP_INDEX);
	free_node(step_index);
	free_node(execution->run_data);

	Node* run_data = get_list_tail(run_stack_list);
	execution->run_data = run_data;
	execution->steps = run_data == NULL ? NULL : get_linked(run_data, STEP);
	execution->step_index = run_data == NULL ? NULL : get_linked(run_data, STEP_INDEX);

	execution->get_step = is_instance_of(execution->steps, LIST) ? get_list_step : get_step;

	return 1;
}

int run_node(ExecutionData* execution, Node* node) {
	Node* runner = get_linked(node, RUNNER);
	if(runner == NULL) {
		link_node(execution->request, RETURN, node);
		return OK;
	}
	else {
		int (*run_func)(ExecutionData*, Node*) = runner->other.pointer;
		return run_func(execution, node);
	}
}

Node* get_create_c_request(Node* request) {
	Node* holder = get_linked(request, EXECUTION_DATA);
	if(holder != NULL) {
		return holder;
	}

	Node* all = get_linked(request, ALL);

	holder = create_node(all);
	link_node(request, EXECUTION_DATA, holder);

	ExecutionData* execution = malloc(sizeof(ExecutionData));
	holder->other.pointer = execution;

	Node* context_stack = get_linked(request, CONTEXT_STACK);
	execution->request = request;
	execution->context_stack = context_stack;
	execution->context = get_list_node_tail(context_stack);
	execution->all = all;
	execution->run_stack = get_linked(request, RUN_STACK);
	execution->run_data = get_list_node_tail(execution->run_stack);
	execution->steps = get_linked(execution->run_data, STEP);
	execution->step_index = get_linked(execution->run_data, STEP_INDEX);

	execution->get_step = is_instance_of(execution->steps, LIST) ? get_list_step : get_step;

	return holder;
}

int run(Node* request) {
	Node* holder = get_create_c_request(request);
	ExecutionData* execution = holder->other.pointer;

/*	Node* log_node = get_linked(request, LOG);
	execution->log = log_node->other.pointer;*/

	Node* step = execution->get_step(execution);
	while(step != NULL) {
		int status = run_node(execution, step);

		if(status != OK) {
			return status;
		}

		++(execution->step_index->part);
		while((step = execution->get_step(execution)) == NULL) {
			if(!pop_run_data(execution)) break;

			if(execution->steps == NULL) break;

			++(execution->step_index->part);
		}
	}

	Node* callback_node = get_linked(request, CALLBACK);
	if(callback_node != NULL) {
		void (*callback)(Node*) = callback_node->other.pointer;
		callback(request);
	}

	free(execution);
	free_node(holder);

	return OK;
}

void signal_request_done(Node* request) {
printf("signal request done\n");

	Node* lock = get_linked(request, LOCK);
	pthread_mutex_t* mutex = lock->other.pointer;

	pthread_mutex_lock(mutex);

	Node* status = get_linked(request, STATUS);
	status->part = REQUEST_DONE;

	Node* cond_holder = get_linked(request, COND);
	pthread_cond_t* cond = cond_holder->other.pointer;
	pthread_cond_signal(cond);

	pthread_mutex_unlock(mutex);
}

void prepare_request_for_waiting(Node* request) {
	Node* all = get_linked(request, ALL);

	Node* status = create_node(all);
	status->part = REQUEST_IN_PROGRESS;
	link_node(request, STATUS, status);

	Node* lock = create_node(all);
	link_node(request, LOCK, lock);
	pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t));
	pthread_mutex_init(mutex, NULL);
	lock->other.pointer = mutex;

	Node* cond_node = create_node(all);
	link_node(request, COND, cond_node);
	pthread_cond_t* cond = malloc(sizeof(pthread_cond_t));
	pthread_cond_init(cond, NULL);
	cond_node->other.pointer = cond;

	Node* callback = create_node(all);
	link_node(request, CALLBACK, callback);
	callback->other.pointer = signal_request_done;
}

void wait_for_request_done(Node* request) {
	Node* lock = get_linked(request, LOCK);
	pthread_mutex_t* mutex = lock->other.pointer;

	Node* cond_holder = get_linked(request, COND);
	pthread_cond_t* cond = cond_holder->other.pointer;

	pthread_mutex_lock(mutex);

	Node* status = get_linked(request, STATUS);
	while(status->part != REQUEST_DONE) {
		pthread_cond_wait(cond, mutex);
	}

	pthread_mutex_unlock(mutex);
}

void free_request_wait_data(Node* request) {
	Node* callback = get_linked(request, CALLBACK);
	free_node(callback);

	unlink_node(request, CALLBACK);

	Node* cond_holder = get_linked(request, COND);
	pthread_cond_destroy(cond_holder->other.pointer);
	free(cond_holder->other.pointer);
	free_node(cond_holder);

	unlink_node(request, COND);
	
	Node* lock = get_linked(request, LOCK);
	pthread_mutex_destroy(lock->other.pointer);
	free(lock->other.pointer);
	free_node(lock);

	unlink_node(request, LOCK);

	Node* status = get_linked(request, STATUS);
	free_node(status);

	unlink_node(request, STATUS);
}

#endif
